package org.wikipedia.theme

import android.content.DialogInterface
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.wikipedia.Constants
import org.wikipedia.Constants.InvokeSource
import org.wikipedia.R
import org.wikipedia.WikipediaApp
import org.wikipedia.activity.FragmentUtil
import org.wikipedia.analytics.eventplatform.AppearanceSettingInteractionEvent
import org.wikipedia.concurrency.FlowEventBus
import org.wikipedia.databinding.DialogThemeChooserBinding
import org.wikipedia.events.WebViewInvalidateEvent
import org.wikipedia.page.ExtendedBottomSheetDialogFragment
import org.wikipedia.settings.Prefs
import org.wikipedia.util.DimenUtil
import org.wikipedia.util.FeedbackUtil
import org.wikipedia.util.ResourceUtil

class ThemeChooserDialog : ExtendedBottomSheetDialogFragment() {
    private var _binding: DialogThemeChooserBinding? = null
    private val binding get() = _binding!!

    interface Callback {
        fun onToggleDimImages()
        fun onToggleReadingFocusMode()
        fun onCancelThemeChooser()
        fun onEditingPrefsChanged()
    }

    private enum class FontSizeAction {
        INCREASE, DECREASE, RESET
    }

    private var app = WikipediaApp.instance
    private lateinit var appearanceSettingInteractionEvent: AppearanceSettingInteractionEvent
    private lateinit var invokeSource: InvokeSource
    private var updatingFont = false
    private var isEditing = false

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = DialogThemeChooserBinding.inflate(inflater, container, false)
        isEditing = requireArguments().getBoolean(EXTRA_IS_EDITING)
        updateComponents()

        binding.textSettingsCategory.text = getString(if (isEditing) R.string.theme_category_editing else R.string.theme_category_reading)
        binding.buttonDecreaseTextSize.setOnClickListener(FontSizeButtonListener(FontSizeAction.DECREASE))
        binding.buttonIncreaseTextSize.setOnClickListener(FontSizeButtonListener(FontSizeAction.INCREASE))
        FeedbackUtil.setButtonTooltip(binding.buttonDecreaseTextSize, binding.buttonIncreaseTextSize)
        binding.buttonThemeLight.setOnClickListener(ThemeButtonListener(Theme.LIGHT))
        binding.buttonThemeDark.setOnClickListener(ThemeButtonListener(Theme.DARK))
        binding.buttonThemeBlack.setOnClickListener(ThemeButtonListener(Theme.BLACK))
        binding.buttonThemeSepia.setOnClickListener(ThemeButtonListener(Theme.SEPIA))
        binding.buttonFontFamilySansSerif.setOnClickListener(FontFamilyListener())
        binding.buttonFontFamilySerif.setOnClickListener(FontFamilyListener())

        binding.themeChooserDarkModeDimImagesSwitch.setOnCheckedChangeListener { _, b -> onToggleDimImages(b) }
        binding.themeChooserMatchSystemThemeSwitch.setOnCheckedChangeListener { _, b -> onToggleMatchSystemTheme(b) }
        binding.themeChooserReadingFocusModeSwitch.setOnCheckedChangeListener { _, b -> onToggleReadingFocusMode(b) }

        binding.textSizeSeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
                if (!fromUser) {
                    return
                }
                val currentMultiplier: Int
                if (isEditing) {
                    currentMultiplier = Prefs.editingTextSizeMultiplier
                    Prefs.editingTextSizeMultiplier = binding.textSizeSeekBar.value
                    callback()?.onEditingPrefsChanged()
                    updateFontSize()
                } else {
                    currentMultiplier = Prefs.textSizeMultiplier
                    val changed = app.setFontSizeMultiplier(binding.textSizeSeekBar.value)
                    if (changed) {
                        updatingFont = true
                        updateFontSize()
                    }
                }
                appearanceSettingInteractionEvent.logFontSizeChange(currentMultiplier.toFloat(), Prefs.textSizeMultiplier.toFloat())
            }

            override fun onStartTrackingTouch(seekBar: SeekBar) {}
            override fun onStopTrackingTouch(seekBar: SeekBar) {}
        })

        binding.syntaxHighlightSwitch.setOnCheckedChangeListener { _, isChecked ->
            Prefs.editSyntaxHighlightEnabled = isChecked
            callback()?.onEditingPrefsChanged()
        }
        binding.monospaceFontSwitch.setOnCheckedChangeListener { _, isChecked ->
            Prefs.editMonoSpaceFontEnabled = isChecked
            callback()?.onEditingPrefsChanged()
        }
        binding.showLineNumbersSwitch.setOnCheckedChangeListener { _, isChecked ->
            Prefs.editLineNumbersEnabled = isChecked
            callback()?.onEditingPrefsChanged()
        }
        binding.typingSuggestionsSwitch.setOnCheckedChangeListener { _, isChecked ->
            Prefs.editTypingSuggestionsEnabled = isChecked
            callback()?.onEditingPrefsChanged()
        }

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                FlowEventBus.events.collectLatest { event ->
                    when (event) {
                        is WebViewInvalidateEvent -> {
                            updatingFont = false
                            updateComponents()
                        }
                    }
                }
            }
        }

        return binding.root
    }

    override fun onStart() {
        super.onStart()
        BottomSheetBehavior.from(requireView().parent as View).peekHeight = (DimenUtil.displayHeightPx * 0.75).toInt()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        invokeSource = requireArguments().getSerializable(Constants.INTENT_EXTRA_INVOKE_SOURCE) as InvokeSource
        appearanceSettingInteractionEvent = AppearanceSettingInteractionEvent(invokeSource)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onCancel(dialog: DialogInterface) {
        super.onCancel(dialog)
        callback()?.onCancelThemeChooser()
    }

    private fun updateForEditing() {
        binding.themeChooserDarkModeDimImagesSwitch.isVisible = !isEditing
        binding.readingFocusModeContainer.isVisible = !isEditing
        binding.themeChooserReadingFocusModeDescription.isVisible = !isEditing
        binding.fontFamilyContainer.isVisible = !isEditing

        binding.syntaxHighlightSwitch.isVisible = isEditing
        binding.monospaceFontSwitch.isVisible = isEditing
        binding.showLineNumbersSwitch.isVisible = isEditing
        binding.typingSuggestionsSwitch.isVisible = isEditing
    }

    private fun onToggleDimImages(enabled: Boolean) {
        if (enabled == Prefs.dimDarkModeImages) {
            return
        }
        Prefs.dimDarkModeImages = enabled
        callback()?.onToggleDimImages()
    }

    private fun onToggleMatchSystemTheme(enabled: Boolean) {
        if (enabled == Prefs.shouldMatchSystemTheme) {
            return
        }
        Prefs.shouldMatchSystemTheme = enabled
        val currentTheme = app.currentTheme
        if (isMatchingSystemThemeEnabled) {
            when (app.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
                Configuration.UI_MODE_NIGHT_YES -> if (!app.currentTheme.isDark) {
                    app.currentTheme = if (!app.unmarshalTheme(Prefs.previousThemeId).isDark) Theme.BLACK else app.unmarshalTheme(Prefs.previousThemeId)
                    Prefs.previousThemeId = currentTheme.marshallingId
                }
                Configuration.UI_MODE_NIGHT_NO -> if (app.currentTheme.isDark) {
                    app.currentTheme = if (app.unmarshalTheme(Prefs.previousThemeId).isDark) Theme.LIGHT else app.unmarshalTheme(Prefs.previousThemeId)
                    Prefs.previousThemeId = currentTheme.marshallingId
                }
            }
        }
        conditionallyDisableThemeButtons()
    }

    private fun onToggleReadingFocusMode(enabled: Boolean) {
        Prefs.readingFocusModeEnabled = enabled
        appearanceSettingInteractionEvent.logReadingFocusMode(enabled)
        callback()?.onToggleReadingFocusMode()
    }

    private fun conditionallyDisableThemeButtons() {
        updateThemeButtonAlpha(binding.buttonThemeLight, isMatchingSystemThemeEnabled && app.currentTheme.isDark)
        updateThemeButtonAlpha(binding.buttonThemeSepia, isMatchingSystemThemeEnabled && app.currentTheme.isDark)
        updateThemeButtonAlpha(binding.buttonThemeDark, isMatchingSystemThemeEnabled && !app.currentTheme.isDark)
        updateThemeButtonAlpha(binding.buttonThemeBlack, isMatchingSystemThemeEnabled && !app.currentTheme.isDark)
        binding.buttonThemeLight.isEnabled = !isMatchingSystemThemeEnabled || !app.currentTheme.isDark
        binding.buttonThemeSepia.isEnabled = !isMatchingSystemThemeEnabled || !app.currentTheme.isDark
        binding.buttonThemeDark.isEnabled = !isMatchingSystemThemeEnabled || app.currentTheme.isDark
        binding.buttonThemeBlack.isEnabled = !isMatchingSystemThemeEnabled || app.currentTheme.isDark
    }

    private fun updateThemeButtonAlpha(button: View, translucent: Boolean) {
        button.alpha = if (translucent) 0.2f else 1.0f
    }

    private val isMatchingSystemThemeEnabled: Boolean
        get() = Prefs.shouldMatchSystemTheme && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q

    private fun updateComponents() {
        updateFontSize()
        updateFontFamily()
        updateThemeButtons()
        updateDimImagesSwitch()
        updateMatchSystemThemeSwitch()
        updateForEditing()

        binding.themeChooserReadingFocusModeSwitch.isChecked = Prefs.readingFocusModeEnabled

        binding.syntaxHighlightSwitch.isChecked = Prefs.editSyntaxHighlightEnabled
        binding.monospaceFontSwitch.isChecked = Prefs.editMonoSpaceFontEnabled
        binding.showLineNumbersSwitch.isChecked = Prefs.editLineNumbersEnabled
        binding.typingSuggestionsSwitch.isChecked = Prefs.editTypingSuggestionsEnabled
    }

    private fun updateMatchSystemThemeSwitch() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            binding.themeChooserMatchSystemThemeSwitch.visibility = View.VISIBLE
            binding.themeChooserMatchSystemThemeSwitch.isChecked = Prefs.shouldMatchSystemTheme
            conditionallyDisableThemeButtons()
        } else {
            binding.themeChooserMatchSystemThemeSwitch.visibility = View.GONE
        }
    }

    private fun updateFontSize() {
        val multiplier = if (isEditing) Prefs.editingTextSizeMultiplier else Prefs.textSizeMultiplier
        binding.textSizeSeekBar.value = multiplier
        val percentStr = getString(R.string.text_size_percent,
                (100 * (1 + multiplier * DimenUtil.getFloat(R.dimen.textSizeMultiplierFactor))).toInt())
        binding.textSizePercent.text = if (multiplier == 0) getString(R.string.text_size_percent_default, percentStr) else percentStr
        if (updatingFont) {
            binding.fontChangeProgressBar.visibility = View.VISIBLE
        } else {
            binding.fontChangeProgressBar.visibility = View.GONE
        }
    }

    private fun updateFontFamily() {
        binding.buttonFontFamilySansSerif.strokeColor = ColorStateList.valueOf(ResourceUtil.getThemedColor(requireContext(), if (Prefs.fontFamily == binding.buttonFontFamilySansSerif.tag) R.attr.progressive_color else R.attr.border_color))
        binding.buttonFontFamilySerif.strokeColor = ColorStateList.valueOf(ResourceUtil.getThemedColor(requireContext(), if (Prefs.fontFamily == binding.buttonFontFamilySerif.tag) R.attr.progressive_color else R.attr.border_color))
    }

    private fun updateThemeButtons() {
        updateThemeButtonStroke(binding.buttonThemeLight, app.currentTheme === Theme.LIGHT)
        updateThemeButtonStroke(binding.buttonThemeSepia, app.currentTheme === Theme.SEPIA)
        updateThemeButtonStroke(binding.buttonThemeDark, app.currentTheme === Theme.DARK)
        updateThemeButtonStroke(binding.buttonThemeBlack, app.currentTheme === Theme.BLACK)
    }

    private fun updateThemeButtonStroke(button: MaterialButton, selected: Boolean) {
        button.strokeWidth = if (selected) BUTTON_STROKE_WIDTH else 0
        button.isClickable = !selected
    }

    private fun updateDimImagesSwitch() {
        val attrColor = if (binding.themeChooserDarkModeDimImagesSwitch.isEnabled) R.attr.secondary_color else R.attr.overlay_color
        binding.themeChooserDarkModeDimImagesSwitch.isChecked = Prefs.dimDarkModeImages
        binding.themeChooserDarkModeDimImagesSwitch.isEnabled = app.currentTheme.isDark
        binding.themeChooserDarkModeDimImagesSwitch.setTextColor(ResourceUtil.getThemedColor(requireContext(), attrColor))
    }

    private inner class ThemeButtonListener(private val theme: Theme) : View.OnClickListener {
        override fun onClick(v: View) {
            if (app.currentTheme !== theme) {
                appearanceSettingInteractionEvent.logThemeChange(app.currentTheme, theme)
                app.currentTheme = theme
            }
        }
    }

    private inner class FontFamilyListener : View.OnClickListener {
        override fun onClick(v: View) {
            if (v.tag != null) {
                val newFontFamily = v.tag as String
                appearanceSettingInteractionEvent.logFontThemeChange(Prefs.fontFamily, newFontFamily)
                app.setFontFamily(newFontFamily)
            }
        }
    }

    private inner class FontSizeButtonListener(private val action: FontSizeAction) : View.OnClickListener {
        override fun onClick(view: View) {
            val currentMultiplier: Int
            if (isEditing) {
                currentMultiplier = Prefs.editingTextSizeMultiplier
                Prefs.editingTextSizeMultiplier = app.constrainFontSizeMultiplier(
                        when (action) {
                            FontSizeAction.INCREASE -> currentMultiplier + 1
                            FontSizeAction.DECREASE -> currentMultiplier - 1
                            FontSizeAction.RESET -> 0
                        })
                callback()?.onEditingPrefsChanged()
                updateFontSize()
            } else {
                currentMultiplier = Prefs.textSizeMultiplier
                val changed = when (action) {
                    FontSizeAction.INCREASE -> {
                        app.setFontSizeMultiplier(Prefs.textSizeMultiplier + 1)
                    }
                    FontSizeAction.DECREASE -> {
                        app.setFontSizeMultiplier(Prefs.textSizeMultiplier - 1)
                    }
                    FontSizeAction.RESET -> {
                        app.setFontSizeMultiplier(0)
                    }
                }
                if (changed) {
                    updatingFont = true
                    updateFontSize()
                }
            }
            appearanceSettingInteractionEvent.logFontSizeChange(currentMultiplier.toFloat(), Prefs.textSizeMultiplier.toFloat())
        }
    }

    fun callback(): Callback? {
        return FragmentUtil.getCallback(this, Callback::class.java)
    }

    companion object {
        private const val EXTRA_IS_EDITING = "isEditing"
        private val BUTTON_STROKE_WIDTH = DimenUtil.roundedDpToPx(2f)

        fun newInstance(source: InvokeSource, isEditing: Boolean = false): ThemeChooserDialog {
            return ThemeChooserDialog().apply {
                arguments = bundleOf(Constants.INTENT_EXTRA_INVOKE_SOURCE to source,
                        EXTRA_IS_EDITING to isEditing)
            }
        }
    }
}
